package dream

import (
	"sync"
	"sync/atomic"
	"time"
)

type EVENT_SRC_ID string

type IQueueItemCtrl interface {
	Drop()
}

type IAutoConnInterfacePre interface {
	OnReady(id int, ctrl IQueueItemCtrl)
	OnPackage(data []byte, xy XY, ctrl IQueueItemCtrl)
	OnClose(ctrl IQueueItemCtrl)
}

type IClientInterfacePre interface {
	OnClientPackage(c IClient, data []byte, xy XY, ctrl IQueueItemCtrl)
	OnClientClose(c IClient, ctrl IQueueItemCtrl)
}

type IAutoConnInterfaceWithId interface {
	OnAutoConnReady(iid EVENT_SRC_ID, id int)
	OnAutoConnPackage(iid EVENT_SRC_ID, data []byte, xy XY)
	OnAutoConnClose(iid EVENT_SRC_ID)
}

type IClientInterfaceWithId interface {
	OnClientPackage(iid EVENT_SRC_ID, c IClient, data []byte, xy XY)
	OnClientClose(iid EVENT_SRC_ID, c IClient)
}

type ITick interface {
	OnTick()
}

type TickFunc func()

func (f TickFunc) OnTick() {
	f()
}

type IRunCtrl1 interface {
	IRun
	RunFunc(f func())
	IStop
}

type IRunCtrl2 interface {
	IRunCtrl1
	SetupAutoConnInterface(iid EVENT_SRC_ID, prevIf IAutoConnInterfacePre) IAutoConnInterface
	SetupClientInterface(iid EVENT_SRC_ID, prevIf IClientInterfacePre) IClientInterface
}

const (
	ev_CLIENT    = 1
	ev_HTTP      = 2
	ev_FUNC      = 3
	ev_USER      = 4
	ev_AUTOCONN  = 5
	ev_NEWCONN   = 6
	ev_CLIENTREG = 7
)

type sEvent struct {
	ev     uint8
	mainid uint32
	client IClient
	xy     XY
	pack   []byte
	iid    EVENT_SRC_ID
	vid    int
	fun    func()
}

var eventSyncPool = sync.Pool{
	New: func() interface{} {
		return &sEvent{}
	},
}

func allocEvent(ev uint8, mainid uint32) *sEvent {
	p := eventSyncPool.Get().(*sEvent)
	p.ev = ev
	p.mainid = mainid
	p.client = nil
	p.fun = nil
	p.pack = nil
	return p
}

func freeEvent(p *sEvent) {
	p.client = nil
	p.fun = nil
	p.pack = nil
	eventSyncPool.Put(p)
}

type sQueueItemCtrl struct {
	droped bool
}

func (s *sQueueItemCtrl) Drop() {
	s.droped = true
}

type sAutoConnHander struct {
	f      IAutoConnInterface
	iid    EVENT_SRC_ID
	runo   *sAsyncRunAble
	prevIf IAutoConnInterfacePre
}

func (self *sAutoConnHander) OnReady(id int) {
	ctrl := sQueueItemCtrl{droped: false}
	if self.prevIf != nil {
		self.prevIf.OnReady(id, &ctrl)
	}
	if !ctrl.droped {
		self.runo.pushAutoReady(self.iid, id)
	}
}
func (self *sAutoConnHander) OnPackage(data []byte, xy XY) {
	ctrl := sQueueItemCtrl{droped: false}
	if self.prevIf != nil {
		self.prevIf.OnPackage(data, xy, &ctrl)
	}
	if !ctrl.droped {
		self.runo.pushAutoPackage(self.iid, data, xy)
	}
}
func (self *sAutoConnHander) OnClose() {
	ctrl := sQueueItemCtrl{droped: false}
	if self.prevIf != nil {
		self.prevIf.OnClose(&ctrl)
	}
	if !ctrl.droped {
		self.runo.pushAutoClose(self.iid)
	}
}

type sClientHander struct {
	f      IClientInterface
	iid    EVENT_SRC_ID
	runo   *sAsyncRunAble
	prevIf IClientInterfacePre
}

func (self *sClientHander) OnClientPackage(c IClient, data []byte, xy XY) {
	ctrl := sQueueItemCtrl{droped: false}
	if self.prevIf != nil {
		self.prevIf.OnClientPackage(c, data, xy, &ctrl)
	}
	if !ctrl.droped {
		self.runo.pushClientPackage(self.iid, c, data, xy)
	}
}
func (self *sClientHander) OnClientClose(c IClient) {
	ctrl := sQueueItemCtrl{droped: false}
	if self.prevIf != nil {
		self.prevIf.OnClientClose(c, &ctrl)
	}
	if !ctrl.droped {
		self.runo.pushClientClose(self.iid, c)
	}
}

type sAsyncRunAble struct {
	timeout     uint32
	cbTick      ITick
	cbAutoConn  IAutoConnInterfaceWithId
	cbClientIf  IClientInterfaceWithId
	ch_event    chan *sEvent
	stoped      int32
	name        string
	mAutoConnIf map[EVENT_SRC_ID]*sAutoConnHander
	mClientIf   map[EVENT_SRC_ID]*sClientHander
}

func (p *sAsyncRunAble) SetupAutoConnInterface(iid EVENT_SRC_ID, prevIf IAutoConnInterfacePre) IAutoConnInterface {
	item, ok := p.mAutoConnIf[iid]
	if ok {
		return item.f
	} else {
		item = &sAutoConnHander{iid: iid, runo: p, prevIf: prevIf}
		item.f = item
		p.mAutoConnIf[iid] = item
		return item.f
	}
}

func (p *sAsyncRunAble) SetupClientInterface(iid EVENT_SRC_ID, prevIf IClientInterfacePre) IClientInterface {
	item, ok := p.mClientIf[iid]
	if ok {
		return item.f
	} else {
		item = &sClientHander{iid: iid, runo: p, prevIf: prevIf}
		item.f = item
		p.mClientIf[iid] = item
		return item.f
	}
}

func (p *sAsyncRunAble) pushClientPackage(iid EVENT_SRC_ID, c IClient, pack []byte, xy XY) {
	if atomic.CompareAndSwapInt32(&p.stoped, 0, 0) {
		e := allocEvent(ev_CLIENT, 1)
		e.iid = iid
		e.client = c
		e.xy = xy
		e.pack = pack
		p.ch_event <- e
	}
}
func (p *sAsyncRunAble) pushClientClose(iid EVENT_SRC_ID, c IClient) {
	if atomic.CompareAndSwapInt32(&p.stoped, 0, 0) {
		e := allocEvent(ev_CLIENT, 2)
		e.client = c
		e.iid = iid
		p.ch_event <- e
	}
}

func (p *sAsyncRunAble) pushAutoReady(iid EVENT_SRC_ID, id int) {
	if atomic.CompareAndSwapInt32(&p.stoped, 0, 0) {
		e := allocEvent(ev_AUTOCONN, 0)
		e.iid = iid
		e.vid = id
		p.ch_event <- e
	}
}
func (p *sAsyncRunAble) pushAutoPackage(iid EVENT_SRC_ID, pack []byte, xy XY) {
	if atomic.CompareAndSwapInt32(&p.stoped, 0, 0) {
		e := allocEvent(ev_AUTOCONN, 1)
		e.iid = iid
		e.xy = xy
		e.pack = pack
		p.ch_event <- e
	}
}
func (p *sAsyncRunAble) pushAutoClose(iid EVENT_SRC_ID) {
	if atomic.CompareAndSwapInt32(&p.stoped, 0, 0) {
		e := allocEvent(ev_AUTOCONN, 2)
		e.iid = iid
		p.ch_event <- e
	}
}

func (p *sAsyncRunAble) RunFunc(f func()) {
	if atomic.CompareAndSwapInt32(&p.stoped, 0, 0) {
		e := allocEvent(ev_FUNC, 0)
		e.fun = f
		p.ch_event <- e
	}
}

func (p *sAsyncRunAble) Stop() {
	atomic.StoreInt32(&p.stoped, 1)
	p.ch_event <- nil
}

func NewQueueRun(n string, spanMs uint32, cbTick ITick, cbClientIf IClientInterfaceWithId, cbAutoConn IAutoConnInterfaceWithId) IRunCtrl2 {
	p := &sAsyncRunAble{
		timeout:     spanMs,
		ch_event:    make(chan *sEvent, 1024*10),
		stoped:      0,
		name:        n,
		cbTick:      cbTick,
		cbClientIf:  cbClientIf,
		cbAutoConn:  cbAutoConn,
		mAutoConnIf: make(map[EVENT_SRC_ID]*sAutoConnHander),
		mClientIf:   make(map[EVENT_SRC_ID]*sClientHander),
	}
	return p
}

func (p *sAsyncRunAble) Run(wg *sync.WaitGroup) {
	if wg != nil {
		wg.Add(1)
	}
	go p.run(wg)
}

func (p *sAsyncRunAble) run(wg *sync.WaitGroup) {
	defer func() { CrashSnap(recover()) }()
	Logi(LOG_KERL, "Queue ", p.name, " run ...")
Main:
	for {
		timeout := time.NewTimer(time.Millisecond * time.Duration(p.timeout))
		select {
		case item := <-p.ch_event:
			if item == nil {
				break Main
			} else if item.ev == ev_CLIENT {
				if item.mainid == 1 {
					p.cbClientIf.OnClientPackage(item.iid, item.client, item.pack, item.xy)
				} else if item.mainid == 2 {
					p.cbClientIf.OnClientClose(item.iid, item.client)
				}
			} else if item.ev == ev_HTTP {
			} else if item.ev == ev_FUNC {
				item.fun()
			} else if item.ev == ev_AUTOCONN {
				if item.mainid == 0 {
					p.cbAutoConn.OnAutoConnReady(item.iid, item.vid)
				} else if item.mainid == 1 {
					p.cbAutoConn.OnAutoConnPackage(item.iid, item.pack, item.xy)
				} else if item.mainid == 2 {
					p.cbAutoConn.OnAutoConnClose(item.iid)
				}
			}
		case <-timeout.C:
			if p.cbTick != nil {
				p.cbTick.OnTick()
			}
		}
	}
	close(p.ch_event)
	Logi(LOG_KERL, " ...Queue ", p.name, " exit")
	if wg != nil {
		wg.Done()
	}
}
